package mirrormonkey.util.annotations.parsing;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

/**
 * Collects <tt>Field</tt>s with same name and type as well as their matching
 * getters and setters.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class BeanFieldIR extends MemberIR {

	/**
	 * IR to collect fields
	 */
	protected FieldIR field;

	/**
	 * IR to collect the getter
	 */
	protected MethodIR getter;

	/**
	 * IR to collect the setter
	 */
	protected MethodIR setter;

	/**
	 * Constant for finding methods with empty parameter list
	 */
	public static final Class<?>[] EMPTY_PARAM_LIST = {};

	/**
	 * Member classes that this <tt>BeanFieldIR</tt> will parse
	 */
	public static final Class<?>[] LISTEN_CLASSES = { Field.class, Method.class };

	/**
	 * Creates a new <tt>BeanFieldIR</tt>. This constructor is called if this
	 * <tt>BeanFieldIR</tt> is the first IR that is created for a <tt>Field</tt>
	 * .
	 * 
	 * @param parser
	 *            the <tt>AnnotationParser</tt> that parses the class hierarchy
	 * @param element
	 *            the <tt>Field</tt> that this <tt>BeanFieldIR</tt> is created
	 *            for. This <tt>BeanFieldIR</tt> will store <tt>element</tt> as
	 *            collected field and will collect every matching getter and
	 *            setter in the class hierarchy.
	 */
	public BeanFieldIR(AnnotationParser parser, Field element) {
		super(parser, element);
		genFrom(element);
	}

	/**
	 * Creates a new <tt>BeanFieldIR</tt>. This constructor is called if this
	 * <tt>BeanFieldIR</tt> is the first IR that is created for the bean field
	 * accessible by <tt>element</tt>.
	 * 
	 * @param parser
	 *            the <tt>AnnotationParser</tt> that parses the class hierarchy
	 * @param element
	 *            the <tt>Method</tt> that this <tt>BeanFieldIR</tt> is created
	 *            for. If <tt>element</tt> is a setter or a getter for a field,
	 *            then the first occurrence of the field in the class hierarchy
	 *            as well as every matching getter and setter will be collected.
	 */
	public BeanFieldIR(AnnotationParser parser, Method element) {
		super(parser, element);
		genFrom(element);
	}

	/**
	 * Creates a new <tt>BeanFieldIR</tt> while copying already collected data
	 * from a previously present, matching <tt>BeanFieldIR</tt> or inheriting
	 * class. This constructor should be called by subclasses to convert
	 * different IR classes into each other.
	 * 
	 * @param previous
	 *            previously present <tt>BeanFieldIR</tt> matching
	 *            <tt>element</tt>
	 * @param element
	 *            the <tt>Field</tt> that this <tt>BeanFieldIR</tt> is created
	 *            for. This <tt>BeanFieldIR</tt> will store <tt>element</tt> as
	 *            collected field and will collect every matching getter and
	 *            setter in the class hierarchy.
	 */
	public BeanFieldIR(BeanFieldIR previous, Field element) {
		super(previous, element);
		field = previous.field;
		getter = previous.getter;
		setter = previous.setter;
	}

	/**
	 * Creates a new <tt>BeanFieldIR</tt> while copying already collected data
	 * from a previously present, matching <tt>BeanFieldIR</tt> or inheriting
	 * class. This constructor should be called by subclasses to convert
	 * different IR classes into each other.
	 * 
	 * @param previous
	 *            previously present <tt>BeanFieldIR</tt> matching
	 *            <tt>element</tt>
	 * @param element
	 *            the <tt>Method</tt> that this <tt>BeanFieldIR</tt> is created
	 *            for. If <tt>element</tt> is a setter or a getter for a field,
	 *            then the first occurrence of the field in the class hierarchy
	 *            as well as every matching getter and setter will be collected.
	 */
	public BeanFieldIR(BeanFieldIR previous, Method element) {
		super(previous, element);
		field = previous.field;
		getter = previous.getter;
		setter = previous.setter;
	}

	/**
	 * Returns the <tt>FieldIR</tt> that will collect the first occurrence of a
	 * matching field in the class hierarchy.
	 * 
	 * @return <tt>FieldIR</tt> collecting the first matching <tt>Field</tt> in
	 *         the class hierarchy and every annotation that should be collected
	 *         from every matching field in the class hierarchy
	 */
	public FieldIR getField() {
		return field;
	}

	/**
	 * Returns the <tt>MethodIR</tt> that will collect the last occurrence of a
	 * matching getter in the class hierarchy.
	 * 
	 * @return <tt>MethodIR</tt> collecting the last matching <tt>Method</tt> in
	 *         the class hierarchy that is a getter for the matching field and
	 *         every annotation that should be collected from every matching
	 *         getter in the class hierarchy
	 */
	public MethodIR getGetter() {
		return getter;
	}

	/**
	 * Returns a <tt>MethodIR</tt> that will collect the last occurrence of a
	 * matching setter in the class hierarchy.
	 * 
	 * @return <tt>MethodIR</tt> collecting the last matching <tt>Method</tt> in
	 *         the class hierarchy that is a setter for the matching field and
	 *         every annotation that should be collected from every matching
	 *         setter in the class hierarchy
	 */
	public MethodIR getSetter() {
		return setter;
	}

	/**
	 * Generates (or re-uses) collectors for <tt>Field</tt>s, getters and
	 * setters that correspond to <tt>f</tt>.
	 * 
	 * @param f
	 *            the <tt>Field</tt> that collected <tt>Field</tt>s, getters and
	 *            setters must correspond to
	 */
	public void genFrom(Field f) {
		field = new FieldIR(parser, f);

		String capName = f.getName().substring(0, 1);
		capName = capName.toUpperCase() + f.getName().substring(1);
		getter = new MethodIR(parser, f.getType(), EMPTY_PARAM_LIST, "get"
				+ capName);
		setter = new MethodIR(parser, void.class,
				new Class<?>[] { f.getType() }, "set" + capName);
	}

	/**
	 * Generates (or re-uses) collectors for <tt>Field</tt>s, getters and
	 * setters that correspond to <tt>m</tt>.<br>
	 * 
	 * <tt>m</tt> must be either a getter or a setter for a field.
	 * 
	 * @param m
	 *            the <tt>Method</tt> that is a getter or setter for the
	 *            <tt>Field</tt> that collected <tt>Field</tt>s, getters and
	 *            setters must correspond to
	 */
	public void genFrom(Method m) {
		String capName = m.getName().substring(3);
		Class<?> type = null;
		if (m.getName().startsWith("get")) {
			type = m.getReturnType();
			getter = new MethodIR(parser, m);
			setter = new MethodIR(parser, void.class, new Class<?>[] { type },
					"set" + capName);
		} else {
			type = m.getParameterTypes()[0];
			getter = new MethodIR(parser, type, EMPTY_PARAM_LIST, "get"
					+ capName);
			setter = new MethodIR(parser, m);
		}

		String fName = capName.substring(0, 1);
		fName = fName.toLowerCase() + capName.substring(1);
		field = new FieldIR(parser, type, fName);
	}

	@Override
	public boolean matches(Member member) {
		return field.matches(member) || getter.matches(member)
				|| setter.matches(member);
	}

	@Override
	public void parseField(Field f) {
		field.setCollectedField(f);
		field.parseAnnotatedElement(f);
		parseAnnotatedElement(f);
	}

	@Override
	public void parseConstructor(Constructor<?> constr) {
		throw new RuntimeException(getClass() + ": Cannot parse Constructors.");
	}

	@Override
	public void parseMethod(Method member) {
		if (getter.matches(member)) {
			getter.setCollectedMethod(member);
			getter.parseAnnotatedElement(member);
		} else {
			setter.setCollectedMethod(member);
			setter.parseAnnotatedElement(member);
		}
		parseAnnotatedElement(member);
	}

	@Override
	public boolean requireReparse() {
		return true;
	}

	@Override
	public Class<?>[] getListenMemberClasses() {
		return LISTEN_CLASSES;
	}

}
